#include <windows.h>
#include <commctrl.h>
#include <strsafe.h>
#include <gdiplus.h>
#include <map>

class CTinyList;
class CTinyColumn
{
public:
    struct Column
    {
        std::basic_string<TCHAR> name;
        int iWidth;
        std::basic_string<TCHAR> icon;
    };

public:
    CTinyColumn(CTinyList *pParent)
    {
        m_pParent = pParent;
        m_hOldCursor = NULL;
        m_bDrag = FALSE;
        m_iCurCol = -1;
    }
    ~CTinyColumn()
    {
    }

public:
    void Init(int iHeight, DWORD dwTextColor, DWORD dwBackColor)
    {
        m_iHeight = iHeight;
        m_dwTextColor = dwTextColor;
        m_dwBackColor = dwBackColor;
    }
    void Insert(int iIndex, const TCHAR *szName, int iWidth, const TCHAR *szIcon = NULL)
    {
        struct Column tCol;
        if (szName)
        {
            tCol.name = szName;
        }
        if (szIcon)
        {
            tCol.icon = szIcon;
        }
        tCol.iWidth = iWidth;
        m_tCols.emplace(iIndex, tCol);
    }
    int GetHeight() { return m_iHeight; }
    int getColumnCount() { return m_tCols.size(); }
    int GetColWidth(int iIndex)
    {
        std::map<int, struct Column>::iterator it = m_tCols.find(iIndex);
        if (it != m_tCols.end())
        {
            return it->second.iWidth;
        }
        return 0;
    }
    void Draw(Gdiplus::Graphics *grx);
    BOOL OnMouseMove(const POINT pt);
    void SubColWidth(int iCol, int iSubWidth)
    {
        std::map<int, CTinyColumn::Column>::iterator it = m_tCols.find(iCol);
        if (it == m_tCols.end())
        {
            return;
        }
        it->second.iWidth += iSubWidth;
        if (it->second.iWidth < 0)
        {
            it->second.iWidth = 0;
        }
    }
    BOOL OnLButtonDown(const POINT pt);
    void CancleDrag(const POINT pt);
    wchar_t *char2wchar(const char *cchar)
    {
        wchar_t *m_wchar;
        int len = MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), NULL, 0);
        m_wchar = new wchar_t[len + 1];
        MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), m_wchar, len);
        m_wchar[len] = '\0';
        return m_wchar;
    }

protected:
    std::map<int, struct Column> m_tCols;
    DWORD m_dwBackColor;
    DWORD m_dwTextColor;
    int m_iHeight;

    CTinyList *m_pParent;
    HCURSOR m_hOldCursor;

    BOOL m_bDrag;
    POINT m_tDragPt;
    int m_iCurCol;
};

class CTinyItem
{
public:
    struct Item
    {
        int iItem;
        std::basic_string<TCHAR> name;
        std::basic_string<TCHAR> icon;
    };

public:
    CTinyItem(CTinyList *pParent) : m_pParent(pParent)
    {
    }
    ~CTinyItem()
    {
    }

public:
    void Init(int iHeight, DWORD dwTextColor, DWORD dwBackColor)
    {
        m_iHeight = iHeight;
        m_dwTextColor = dwTextColor;
        m_dwBackColor = dwBackColor;
    }
    void Insert(int iItem, TCHAR *szName, const TCHAR *szIcon = NULL)
    {
        Item tItem;
        tItem.iItem = iItem;
        if (szName)
        {
            tItem.name = szName;
        }
        if (szIcon)
        {
            tItem.icon = szIcon;
        }
        m_tItems.emplace(iItem, tItem);
    }
    void Draw(Gdiplus::Graphics &grx, int iItem, const RECT rc, DWORD dwTxtColor);
    wchar_t *char2wchar(const char *cchar)
    {
        wchar_t *m_wchar;
        int len = MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), NULL, 0);
        m_wchar = new wchar_t[len + 1];
        MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), m_wchar, len);
        m_wchar[len] = '\0';
        return m_wchar;
    }

public:
    std::map<int, struct Item> m_tItems;
    DWORD m_dwBackColor;
    DWORD m_dwTextColor;
    int m_iHeight;

    CTinyList *m_pParent;
};

class CTinyList
{
public:
    CTinyList() : m_cColumn(this)
    {
        m_iItemHover = -1;
        m_iItemCurrent = -1;
    }
    ~CTinyList()
    {
    }

public:
    void Init(int iHeight, DWORD dwBackColor, DWORD dwTxtColor)
    {
        m_cColumn.Init(iHeight, dwBackColor, dwTxtColor);
    }
    void SetBound(const RECT bound)
    {
        m_bound = bound;
    }
    const RECT &GetBound() { return m_bound; }
    void SetBackColor(DWORD dwBackColor)
    {
        m_dwBackColor = dwBackColor;
    }
    void SetSelItemBColor(DWORD dwSelColor)
    {
        m_dwSelItemBColor = dwSelColor;
    }
    void SetTxtColor(DWORD dwTxtCor)
    {
        m_dwTxtColor = dwTxtCor;
    }
    void SetFamily(const TCHAR *szFamily)
    {
        if (szFamily)
        {
            m_strFamily = szFamily;
        }
    }
    const TCHAR *GetFamily() { return m_strFamily.c_str(); }
    void SetFontSize(DWORD dwFontSize)
    {
        m_dwFontSize = dwFontSize;
    }
    DWORD GetFontSize() { return m_dwFontSize; }
    void SetItemHeight(int iHeight1, int iHeight2)
    {
        m_iItemHeight1 = iHeight1;
        m_iItemHeight2 = iHeight2;
    }
    void InsertColumn(int iIndex, const TCHAR *szName, int iWidth, const TCHAR *szIcon = NULL)
    {
        m_cColumn.Insert(iIndex, szName, iWidth, szIcon);
    }
    void InsertItem(int iItem, TCHAR *szName, TCHAR *szIcon = NULL)
    {
        CTinyItem *pItem = new CTinyItem(this);
        pItem->Insert(0, szName, szIcon);
        m_tItems.emplace(iItem, pItem);
    }
    void SetItemText(int iItem, int iSubItem, TCHAR *szName, TCHAR *szIcon = NULL)
    {
        std::map<int, CTinyItem *>::iterator it = m_tItems.find(iItem);
        if (it != m_tItems.end())
        {
            it->second->Insert(iSubItem, szName, szIcon);
        }
    }
    void DrawItems(Gdiplus::Graphics &grx)
    {
        int iTop = m_cColumn.GetHeight() + m_bound.top;
        std::map<int, CTinyItem *>::iterator it = m_tItems.begin();
        while (it != m_tItems.end())
        {
            int iHeight = m_iItemHeight1;
            DWORD dwBackColor = m_dwBackColor;
            DWORD dwTxtColor = m_dwTxtColor;
            DWORD dwA = 50;

            //mouseHover
            if (m_iItemHover == it->first)
            {
                iHeight = m_iItemHeight2;
                dwBackColor = m_dwSelItemBColor;
                dwTxtColor = RGB(255, 255, 255);
            }
            else if (m_iItemCurrent == it->first)
            {
                iHeight = m_iItemHeight2;
                dwBackColor = m_dwSelItemBColor;
                dwTxtColor = RGB(255, 255, 255);
                dwA = 80;
            }

            RECT rcItem;
            rcItem.left = m_bound.left;
            rcItem.top = iTop;
            rcItem.right = m_bound.right;
            rcItem.bottom = iTop + iHeight;

            RECT rcVisible;
            IntersectRect(&rcVisible, &rcItem, &m_bound);
            if (IsRectEmpty(&rcVisible))
            {
                break;
            }

            //mouseHoverBackColor
            Gdiplus::SolidBrush fillBrush(Gdiplus::Color(dwA, GetRValue(dwBackColor), GetGValue(dwBackColor), GetBValue(dwBackColor)));
            grx.FillRectangle(&fillBrush, Gdiplus::Rect(rcItem.left, rcItem.top, rcItem.right - rcItem.left, rcItem.bottom - rcItem.top));

            int iLeft = rcItem.left;
            int iCols = m_cColumn.getColumnCount();
            for (int i = 0; i < iCols; i++)
            {
                RECT rcSubItem;
                rcSubItem.left = iLeft;
                rcSubItem.top = rcItem.top;
                rcSubItem.right = iLeft + m_cColumn.GetColWidth(i);
                rcSubItem.bottom = rcItem.bottom;

                it->second->Draw(grx, i, rcSubItem, dwTxtColor);

                iLeft += m_cColumn.GetColWidth(i);
            }

            iTop += iHeight;
            ++it;
        }
    }
    void SetHostWnd(HWND hWnd) { m_hHostWnd = hWnd; }
    HWND GetHostWnd() { return m_hHostWnd; }

    void DoDraw(HDC hdc)
    {
        Gdiplus::Graphics graphics(hdc);
        // graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
        int iWidth = m_bound.right - m_bound.left;
        int iHeight = m_bound.bottom - m_bound.top;
        Gdiplus::Bitmap membmp(iWidth, iHeight);
        Gdiplus::Graphics *pGrx = Gdiplus::Graphics::FromImage(&membmp);

        Gdiplus::SolidBrush fillBursh(Gdiplus::Color(GetRValue(m_dwBackColor), GetRValue(m_dwBackColor), GetRValue(m_dwBackColor)));
        pGrx->FillRectangle(&fillBursh, Gdiplus::Rect(m_bound.left, m_bound.top, m_bound.right - m_bound.left, m_bound.bottom - m_bound.top));

        //列头
        m_cColumn.Draw(pGrx);

        //画行
        DrawItems(*pGrx);

        //显示
        graphics.DrawImage(&membmp, m_bound.left, m_bound.top, 0, 0, iWidth, iHeight, Gdiplus::UnitPixel);

        delete pGrx;
        pGrx = NULL;
    }
    void OnLButtonDown(HWND hWnd, const POINT pt)
    {
        m_hHostWnd = hWnd;
        if (!PtInRect(&m_bound, pt))
        {
            return;
        }
        if (m_cColumn.OnLButtonDown(pt))
        {
            return;
        }

        int iItem = FindItemByPoint(pt);
        if (iItem >= 0)
        {
            m_iItemCurrent = iItem;
            InvalidateRect(hWnd, &m_bound, TRUE);
        }
    }
    void OnLButtonUp(HWND hWnd, const POINT pt)
    {
        m_cColumn.CancleDrag(pt);
    }
    void OnMouseMove(HWND hWnd, const POINT pt)
    {
        m_hHostWnd = hWnd;
        if (!PtInRect(&m_bound, pt))
        {
            return;
        }

        if (m_cColumn.OnMouseMove(pt))
        {
            InvalidateRect(hWnd, &m_bound, TRUE);
            return;
        }

        int iItem = FindItemByPoint(pt);
        if (iItem >= 0)
        {
            m_iItemHover = iItem;
            InvalidateRect(hWnd, &m_bound, TRUE);
        }
    }
    int FindItemByPoint(const POINT pt)
    {
        int iItem = 0;
        int iTop = m_bound.top + m_cColumn.GetHeight();
        std::map<int, CTinyItem *>::iterator it = m_tItems.begin();
        while (it != m_tItems.end())
        {
            int iHeight = m_iItemHeight1;
            if (it->first == m_iItemHover)
            {
                iHeight = m_iItemHeight2;
            }
            RECT rcItem;
            rcItem.left = m_bound.left;
            rcItem.top = iTop;
            rcItem.right = m_bound.right;
            rcItem.bottom = iTop + iHeight;

            if (PtInRect(&rcItem, pt))
            {
                iItem = it->first;
                break;
            }

            iTop += iHeight;
            ++it;
            //超出最底部
            if (iTop >= m_bound.bottom)
            {
                break;
            }
        }
        return iItem;
    }

public:
    CTinyColumn m_cColumn;
    std::map<int, CTinyItem *> m_tItems;
    RECT m_bound;
    DWORD m_dwBackColor;
    DWORD m_dwSelItemBColor;
    DWORD m_dwTxtColor;
    HWND m_hHostWnd;

    //字体
    std::basic_string<TCHAR> m_strFamily;
    DWORD m_dwFontSize;
    int m_iItemHeight1;
    int m_iItemHeight2;

    //鼠标
    int m_iItemHover;
    int m_iItemCurrent;
};

// 下面两个函数只能类外实现
void CTinyColumn::Draw(Gdiplus::Graphics *grx)
{
    RECT rcParent = m_pParent->GetBound();

    //绘制背景
    Gdiplus::SolidBrush fillBrush(Gdiplus::Color(GetRValue(m_dwBackColor), GetRValue(m_dwBackColor), GetRValue(m_dwBackColor)));
    grx->FillRectangle(&fillBrush, rcParent.left, rcParent.top, rcParent.right - rcParent.left, m_iHeight);

    //绘制标题
    // Gdiplus::FontFamily family(m_pParent->GetFamily());
    Gdiplus::FontFamily family(L"Arial");
    Gdiplus::Font font(&family, m_pParent->GetFontSize());

    //对其格式
    Gdiplus::StringFormat format;
    format.SetAlignment(Gdiplus::StringAlignmentNear);
    format.SetLineAlignment(Gdiplus::StringAlignmentCenter);
    format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap);

    //刷子
    Gdiplus::SolidBrush txtBrush(Gdiplus::Color(GetRValue(m_dwTextColor), GetRValue(m_dwTextColor), GetRValue(m_dwTextColor)));

    //循环画列头
    std::map<int, struct Column>::iterator it = m_tCols.begin();
    int iLeft = rcParent.left;
    while (it != m_tCols.end())
    {
        if (it->second.iWidth != 0)
        {
            Gdiplus::RectF rcTxt(iLeft, rcParent.top, it->second.iWidth, m_iHeight);
            grx->DrawString(char2wchar((char *)it->second.name.c_str()), (INT)it->second.name.length(), &font, rcTxt, &format, &txtBrush);
            //画竖线
            iLeft += it->second.iWidth;
            Gdiplus::Pen pen(Gdiplus::Color(100, 0, 0, 0), 1);
            grx->DrawLine(&pen, Gdiplus::Point(iLeft, rcParent.top + 4), Gdiplus::Point(iLeft, rcParent.top + m_iHeight - 4));
        }
        ++it;
    }
};
BOOL CTinyColumn::OnLButtonDown(const POINT pt)
{
    const RECT rcParent = m_pParent->GetBound();
    //列头坐标
    RECT rcCol;
    rcCol.left = rcParent.left;
    rcCol.top = rcParent.top;
    rcCol.right = rcParent.right;
    rcCol.bottom = rcParent.top + m_iHeight;
    //判断鼠标是否在此地方
    if (!PtInRect(&rcCol, pt))
    {
        m_bDrag = FALSE;
        return FALSE;
    }
    //是否按下鼠标
    int iLeft = rcCol.left;
    std::map<int, CTinyColumn::Column>::iterator it = m_tCols.begin();
    while (it != m_tCols.end())
    {
        rcCol.left = iLeft + it->second.iWidth - 2;
        rcCol.right = rcCol.left + 4;
        if (PtInRect(&rcCol, pt))
        {
            //找到了
            m_bDrag = TRUE;
            m_tDragPt = pt;
            m_iCurCol = it->first;
            return TRUE;
        }
        iLeft += it->second.iWidth;
        ++it;
    }
    return FALSE; 
}
void CTinyColumn::CancleDrag(const POINT pt)
{
    if (m_bDrag)
    {
        m_bDrag = FALSE;
        m_iCurCol = -1;
        if (m_hOldCursor)
        {
            SetClassLongPtr(m_pParent->GetHostWnd(), GCLP_HCURSOR, (LONG_PTR)m_hOldCursor);
            m_hOldCursor = NULL;
        }
    }
}
BOOL CTinyColumn::OnMouseMove(const POINT pt)
{
    const RECT rcParent = m_pParent->GetBound();
    //列头坐标
    RECT rcCol;
    rcCol.left = rcParent.left;
    rcCol.top = rcParent.top;
    rcCol.right = rcParent.right;
    rcCol.bottom = rcParent.top + m_iHeight;
    //判断鼠标是否在此地方
    if (!PtInRect(&rcCol, pt))
    {
        if (m_hOldCursor)
        {
            SetClassLongPtr(m_pParent->GetHostWnd(), GCLP_HCURSOR, (LONG_PTR)m_hOldCursor);
            m_hOldCursor = NULL;
        }
    }
    if (m_bDrag)
    {
        int iSubWitdh = pt.x - m_tDragPt.x;

        SubColWidth(m_iCurCol, iSubWitdh);

        m_tDragPt = pt;
        return TRUE;
    }

    int iLeft = rcCol.left;
    std::map<int, CTinyColumn::Column>::iterator it = m_tCols.begin();
    while (it != m_tCols.end())
    {
        rcCol.left = iLeft + it->second.iWidth - 2;
        rcCol.right = rcCol.left + 4;
        if (PtInRect(&rcCol, pt))
        {
            //找到了
            if (m_hOldCursor == NULL)
            {
                m_hOldCursor = (HCURSOR)SetClassLongPtr(m_pParent->GetHostWnd(), GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, IDC_SIZEWE));
            }
            return TRUE;
        }
        iLeft += it->second.iWidth;
        ++it;
    }
    //不在任何两列头之间 恢复旧光标
    if (m_hOldCursor)
    {
        SetClassLongPtr(m_pParent->GetHostWnd(), GCLP_HCURSOR, (LONG_PTR)m_hOldCursor);
        m_hOldCursor = NULL;
    }
    return FALSE;
}
void CTinyItem::Draw(Gdiplus::Graphics &grx, int iItem, const RECT rc, DWORD dwTxtColor)
{
    if (rc.left == rc.right)
    {
        return;
    }
    std::map<int, struct Item>::iterator it = m_tItems.find(iItem);
    if (it == m_tItems.end())
    {
        return;
    }

    //绘制标题
    // Gdiplus::FontFamily family(m_pParent->GetFamily());
    Gdiplus::FontFamily family(L"Arial");
    Gdiplus::Font font(&family, m_pParent->GetFontSize());

    //对其格式
    Gdiplus::StringFormat format;
    format.SetAlignment(Gdiplus::StringAlignmentNear);
    format.SetLineAlignment(Gdiplus::StringAlignmentCenter);
    format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap);

    //刷子
    Gdiplus::SolidBrush txtBrush(Gdiplus::Color(GetRValue(dwTxtColor), GetRValue(dwTxtColor), GetRValue(dwTxtColor)));

    //画文字
    grx.DrawString(char2wchar((char *)it->second.name.c_str()), (INT)it->second.name.length(), &font,
                   Gdiplus::RectF(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top), &format, &txtBrush);
}